/*
	File:		GameStateMachine.cpp
	Author:		Arturo Mata, dmonroe
	Date:		11/4/2012
	Copyright:	Full Sail University

	Purpose:	General purpose game state machine which switches between IGameStates
*/

#include "../source/IGameStateMachine.h"
#include "../source/IGameState.h"

#include <cassert>
#include <list>
#include <map>
using std::list;
using std::map;


//******************************************************************
// CGameStateMachine
//	- concrete class for handling game states
class CGameStateMachine : public IGameStateMachine
{
public:
	// SINGLETON helper:
	friend	IGameStateMachine*	IGameStateMachine::GetInstance		( void );
	friend	void				IGameStateMachine::DeleteInstance	( void );

	
	// SETUP
	virtual bool		Initialize		( void );
	virtual bool		Terminate		( void );
	
	// STATE ID
	//enum EStateID		{ E_MAINMENU, E_GAMEPLAY, E_OPTIONS, E_HOWTOPLAY, E_HIGHSCORES, E_CREDITS };
	virtual bool		RegisterState	( EStateID ID, IGameState* (*pFunction)( void ) );

	// STATE MACHINE
	virtual bool		SwitchState		( EStateID ID );
	virtual bool		PushState		( EStateID ID );
	virtual bool		PopState		( void );
	virtual bool		PopAllStates	( void );

	// FRAME UPDATE
	virtual bool		Input			( float fElapsedTime );
	virtual bool		Update			( float fElapsedTime );
	virtual bool		Render			( float fElapsedTime );

	
private:
	// SINGLETON
	static CGameStateMachine*		s_Instance;	

	CGameStateMachine				( void );				// Default constructor
	virtual	~CGameStateMachine		( void );				// Destructor

	CGameStateMachine				( const CGameStateMachine& );		// Copy constructor
	CGameStateMachine&	operator=	( const CGameStateMachine& );		// Assignment operator

	
	// Wrapper Status
	enum EStatus
	{	
		E_UNINITIALIZED,
		E_INITIALIZED,
		E_DESTROYED
	};
	EStatus						m_eStatus;				// initialization status


	// States
	list< IGameState* >			m_GameStates;			// stack-based state machine

	map< EStateID, IGameState* (*)( void ) >	m_IDs;	// id->state map
};
//******************************************************************



//******************************************************************
// SINGLETON

// Instantiate static pointer to null (no instance yet)
/*static*/ CGameStateMachine*		CGameStateMachine::s_Instance		= NULL;

// Interface singleton accessor
/*static*/ IGameStateMachine*		IGameStateMachine::GetInstance		( void )
{
	// Allocate singleton on first use
	if( CGameStateMachine::s_Instance == NULL )
		CGameStateMachine::s_Instance = new CGameStateMachine;

	// Return the singleton
	return CGameStateMachine::s_Instance;
}

// Interface singleton destructor
/*static*/ void			IGameStateMachine::DeleteInstance	( void )
{
	// Deallocate singleton
	delete CGameStateMachine::s_Instance;
	CGameStateMachine::s_Instance = NULL;
}
//******************************************************************



//******************************************************************
// CONSTRUCTOR
CGameStateMachine::CGameStateMachine(void)
{
	// Set data members to 'clean' values
	m_eStatus = E_UNINITIALIZED;
}
//******************************************************************



//******************************************************************
// DESTRUCTOR
/*virtual*/	CGameStateMachine::~CGameStateMachine(void)
{
	// Release memory cleanly in Release mode
#if !defined( DEBUG ) && !defined( _DEBUG )
	if( m_eStatus == E_INITIALIZED )
		Terminate();
#endif
}
//******************************************************************



//******************************************************************
// INITIALIZE
/*virtual*/	bool CGameStateMachine::Initialize( void )
{
	// Sanity-check the state machine's status
	assert( m_eStatus == E_UNINITIALIZED && "CGameStateMachine::Initialize - state machine has already been initialized" != 0 );

	
	// Success!
	m_eStatus = E_INITIALIZED;
	return true;
}
//******************************************************************



//******************************************************************
// TERMINATE
/*virtual*/	bool CGameStateMachine::Terminate( void )
{
	// Sanity-check the state machine's status
	assert( m_eStatus == E_INITIALIZED && "CGameStateMachine::Terminate - state machine has not been initialized" != 0 );

	// Exit all states
	PopAllStates();

	// Unregister all states
	m_IDs.clear();

	// Wrapper is now destroyed
	m_eStatus = E_DESTROYED;
	return true;
}
//******************************************************************



//******************************************************************
// REGISTER STATE
/*virtual*/ bool CGameStateMachine::RegisterState( EStateID ID, IGameState* (*pFunction)( void ) )
{
	// Sanity-check the state machine's status
	assert( m_eStatus == E_INITIALIZED && "CGameStateMachine::RegisterState - state machine has not been initialized" != 0 );

	// Validate the callback function parameter
	assert( pFunction != NULL && "CGameStateMachine::RegisterState - callback function cannot be null" != 0 );


	// Check if the state has already been registered
	if( m_IDs[ ID ] != NULL && m_IDs[ ID ] != pFunction )
	{
		// MESSAGE
		return false;
	}

	// Add the function pointer to the map
	m_IDs[ ID ] = pFunction;

	return true;
}
//******************************************************************



//******************************************************************
// SWITCH STATE
/*virtual*/ bool CGameStateMachine::SwitchState( EStateID ID )
{
	// Exit one state
	PopState();

	// Enter new state
	return PushState( ID );
}
//******************************************************************



//******************************************************************
// PUSH STATE
/*virtual*/ bool CGameStateMachine::PushState( EStateID ID ) 
{
	// Sanity-check the state machine's status
	assert( m_eStatus == E_INITIALIZED && "CGameStateMachine::PushState - state machine has not been initialized" != 0 );


	// Look up the state
	IGameState* pNewState = m_IDs[ ID ]();

	// Enter the new state
	if( pNewState != NULL )
	{
		m_GameStates.push_back( pNewState );
		return pNewState->Enter();
	}
	else
		return false;
}
//******************************************************************



//******************************************************************
// POP STATE
/*virtual*/ bool CGameStateMachine::PopState()
{
	// Sanity-check the state machine's status
	assert( m_eStatus == E_INITIALIZED && "CGameStateMachine::PopState - state machine has not been initialized" != 0 );


	// Exit one state
	if( m_GameStates.empty() != true )
	{
		m_GameStates.back()->Exit();
		m_GameStates.back()->DeleteInstance();
		m_GameStates.pop_back();
	}

	return true;
}
//******************************************************************



//******************************************************************
// POP ALL STATES
/*virtual*/	bool CGameStateMachine::PopAllStates( void )
{
	// Sanity-check the state machine's status
	assert( m_eStatus == E_INITIALIZED && "CGameStateMachine::PopAllStates - state machine has not been initialized" != 0 );

	// Exit all states
	while( m_GameStates.empty() != true )
	{
		m_GameStates.back()->Exit();
		m_GameStates.back()->DeleteInstance();
		m_GameStates.pop_back();
	}

	return true;
}
//******************************************************************



//******************************************************************
// INPUT
/*virtual*/	bool CGameStateMachine::Input( float fElapsedTime )
{
	// Only let the top state process input
	IGameState* pCurrent = m_GameStates.back();
	return pCurrent->Input( fElapsedTime );
}
//******************************************************************



//******************************************************************
// UPDATE
/*virtual*/	bool CGameStateMachine::Update( float fElapsedTime )
{
	// Only let the top state update
	IGameState* pCurrent = m_GameStates.back();
	return pCurrent->Update( fElapsedTime );
}
//******************************************************************

 

//******************************************************************
// RENDER
/*virtual*/	bool CGameStateMachine::Render( float fElapsedTime )
{
	// Render all states
	list< IGameState* >::iterator iter;

	// NOTE: Do we care about any of the lower states failing?
	bool result = true;

	for( iter = m_GameStates.begin(); iter != m_GameStates.end(); ++iter )
		result = (*iter)->Render( fElapsedTime );

	return result;
}
//******************************************************************
